#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
日志模块，不支持多进程。
如果需要多进程，建议在logging.conf通过进程ID"str(os.getpid())"区分文件。
"""
import os
import sys
import time
import logging.handlers
import logging.config
import configparser
import gzip
import threading
from stat import ST_MTIME

CONF_PATH = "/etc/expontech/obs-manager/csm_manager.conf"
if not os.path.exists(CONF_PATH):
    # 为了规避CICD问题
    CONF_PATH = os.path.join(
        os.path.dirname(__file__),
        "../../etc/expontech/obs-manager/csm_manager.conf"
    )

LOG_KEY = "dashboard"
UPDATE_CONFIG_TIMER = 60

LOG_PATH = "/var/log/expontech/obs-manager/"
if not os.path.exists(LOG_PATH):
    os.makedirs(LOG_PATH)

read_conf_ok = False


class GzTimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler):
    """
    支持压缩的TimedRotatingFileHandler类
    """

    @staticmethod
    def do_gzip(old_log):
        # 压缩时启动一个线程去做。
        def handle(old_log_name):
            with open(old_log_name, "rb") as f_in:
                with gzip.open(old_log_name + ".gz", "wb") as f_out:
                    f_out.writelines(f_in)

            os.remove(old_log_name)

        th = threading.Thread(target=handle, args=(old_log,))
        th.start()

    def doRollover(self):
        if self.stream:
            self.stream.close()
            self.stream = None
        # get the time that this sequence started at and make it a TimeTuple
        current_time = int(time.time())
        dst_now = time.localtime(current_time)[-1]
        t = self.rolloverAt - self.interval
        if self.utc:
            time_tuple = time.gmtime(t)
        else:
            time_tuple = time.localtime(t)
            dst_then = time_tuple[-1]
            if dst_now != dst_then:
                if dst_now:
                    addend = 3600
                else:
                    addend = -3600
                time_tuple = time.localtime(t + addend)
        dfn = self.baseFilename + "." + time.strftime(self.suffix, time_tuple)
        if os.path.exists(dfn):
            os.remove(dfn)
        # Issue 18940: A file may not have been created if delay is True.
        if os.path.exists(self.baseFilename):
            os.rename(self.baseFilename, dfn)
            self.do_gzip(dfn)
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)
        if not self.delay:
            self.stream = self._open()
        new_rollover_at = self.computeRollover(current_time)
        while new_rollover_at <= current_time:
            new_rollover_at = new_rollover_at + self.interval
        if (self.when == "MIDNIGHT" or self.when.startswith(
                "W")) and not self.utc:
            dst_at_rollover = time.localtime(new_rollover_at)[-1]
            if dst_now != dst_at_rollover:
                if not dst_now:
                    addend = -3600
                else:
                    addend = 3600
                new_rollover_at += addend
        self.rolloverAt = new_rollover_at


class IniConfigure(object):
    """
        ini 文件操作基类，需要使用时，由此类派生。
        example:
            class DBConfigure(IniConfigure):
    """

    def __init__(self, path):
        self._conf_path = path

        self._config = configparser.ConfigParser()
        self._read_conf()

    def _add_section(self, section):
        if not self._config.has_section(section):
            self._config.add_section(section)

    def get_item(self, section, item):
        try:
            ret = self._config.get(section, item)
        except configparser.NoSectionError as ex:
            print("read ini file failed! e=[{}]".format(ex))
            ret = ""
        except configparser.NoOptionError as ex:
            print("read ini file failed! e=[{}]".format(ex))
            ret = ""
        except configparser.Error as ex:
            print("read ini file failed! e=[{}]".format(ex))
            ret = ""

        return ret

    def set_item(self, section, item, value):
        try:
            self._add_section(section)
            self._config.set(section, item, str(value))
        except configparser.NoSectionError:
            pass

    def write_ini(self, out=None):
        # folder = os.path.dirname(self._conf_path)
        # if not os.path.exists(folder):
        #     os.mkdir(folder)
        if not out:
            out = self._conf_path
        with open(out, "w", encoding="utf-8") as file:
            self._config.write(file)

    def _read_conf(self):
        if os.path.exists(self._conf_path):
            try:
                self._config.read(self._conf_path, encoding="utf-8")
            except OSError as e:
                print("load configure failed! e={}".format(e))
        else:
            print("configure file[{}] is missing!".format(self._conf_path))

    def write(self, out=None):
        self.write_ini(out)


class LogConfig(IniConfigure):
    """
    日志配置文件
    """
    __section__ = "log_config"

    def __init__(self, path):
        super(LogConfig, self).__init__(path)

        self._console = "CRITICAL"
        self._log_info = "INFO"
        self._log_error = "ERROR"

        self.get_db_conf()

    def get_db_conf(self):
        self._console = self.get_item(self.__section__, "console") \
                        or self._console
        self._log_info = self.get_item(self.__section__, "info") \
                         or self._log_info
        self._log_error = self.get_item(self.__section__, "error") \
                          or self._log_error

    def refresh(self):
        self._read_conf()
        self.get_db_conf()

    @property
    def console(self):
        return self._console

    @property
    def log_info(self):
        return self._log_info

    @log_info.setter
    def log_info(self, value):
        self.set_item(self.__section__, "info", value)
        self.write()

    @property
    def log_error(self):
        return self._log_error


handler_info = None
handler_error = None
handle_std = None
try:
    log_conf = LogConfig(CONF_PATH)

    # 定时任务框架日志级别调整为Warning
    logging.getLogger("apscheduler.executors.default").setLevel(logging.WARNING)

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    logfile_info = os.path.join(LOG_PATH, "manager.log")
    logfile_error = os.path.join(LOG_PATH, "manager.error.log")

    standard_form = logging.Formatter(
        "%(asctime)s %(levelname)-8s %(process)d %(thread)d "
        # "%(filename)s %(lineno)d %(message)s")
        "%(pathname)s %(lineno)d %(message)s")

    # 创建hander 用于写日志文件
    # 日志按时间分割，info日志
    handler_info = GzTimedRotatingFileHandler(
        logfile_info, "H", 1, 24 * 30, "utf-8"
    )
    handler_info.setLevel(log_conf.log_info)
    handler_info.setFormatter(standard_form)

    # 日志按时间分割，error日志
    handler_error = GzTimedRotatingFileHandler(
        logfile_error, "H", 1, 24 * 30, "utf-8"
    )
    handler_error.setLevel(log_conf.log_error)
    handler_error.setFormatter(standard_form)

    # 再创建控制台hander debug日志
    handle_std = logging.StreamHandler(sys.stdout)
    handle_std.setLevel(log_conf.console)
    handle_std.setFormatter(standard_form)

    # 将全部handle添加到logger中
    logger.addHandler(handle_std)
    logger.addHandler(handler_info)
    logger.addHandler(handler_error)

    conf_change_time_init = os.stat(CONF_PATH)[ST_MTIME]

except Exception as ex:
    print(f"initial log module failed!e={ex}")


# 启动定时线程，定期读取配置文件，便于在程序运行时，修改日志级别
def load_log_config():
    global conf_change_time_init
    if os.path.exists(CONF_PATH):
        conf_change_time_current = os.stat(CONF_PATH)[ST_MTIME]
        # 只有配置文件发生变化了（文件修改时间发生变化），才重新读取。
        if conf_change_time_current != conf_change_time_init:
            try:
                # 将新的文件变更时间再次记录下来。
                conf_change_time_init = conf_change_time_current

                log_conf.refresh()
                handler_info.setLevel(log_conf.log_info)
                handler_error.setLevel(log_conf.log_error)
                handle_std.setLevel(log_conf.console)
            except Exception as ex:
                logger.warning(
                    "Read log configfile({}) failed. Reason: {} {} ".format(
                        os.path.basename(CONF_PATH),
                        type(ex),
                        ex))

